// SPDX-FileCopyrightText: Copyright 2023 citrus Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <dlfcn.h>
#include <sys/socket.h>
#include <android/binder_parcel.h>
#include <android/binder_parcel_jni.h>

static const int fileDescriptorFd = 3;

static const auto marshalParcel = []() -> binder_status_t(*)(const AParcel*, uint8_t*, size_t, size_t) {
    if (void* func = dlsym(nullptr, "AParcel_marshal"))
        return reinterpret_cast<binder_status_t(*)(const AParcel*, uint8_t*, size_t, size_t)>(func);
    return [](const AParcel* parcel, uint8_t* buffer, size_t start, size_t len) -> binder_status_t {
        buffer += start;
        AParcel_setDataPosition(parcel, 0);
        for (size_t i = 0; i < len / sizeof(int64_t); ++i)
            AParcel_readInt64(parcel, reinterpret_cast<int64_t*&>(buffer)++);
        if (len & 4)
            AParcel_readInt32(parcel, reinterpret_cast<int32_t*&>(buffer)++);
        if (len & 2)
            AParcel_readChar(parcel, reinterpret_cast<char16_t*&>(buffer)++);
        if (len & 1)
            AParcel_readByte(parcel, reinterpret_cast<int8_t*&>(buffer));
        return STATUS_OK;
    };
}();

static const auto unmarshalParcel = []() -> binder_status_t(*)(AParcel*, const uint8_t*, size_t) {
    if (void* func = dlsym(nullptr, "AParcel_unmarshal"))
        return reinterpret_cast<binder_status_t(*)(AParcel*, const uint8_t*, size_t)>(func);
    return [](AParcel* parcel, const uint8_t* buffer, size_t len) -> binder_status_t {
        AParcel_reset(parcel);
        for (size_t i = 0; i < len / sizeof(int64_t); ++i)
            AParcel_writeInt64(parcel, *reinterpret_cast<const int64_t*&>(buffer)++);
        if (len & 4)
            AParcel_writeInt32(parcel, *reinterpret_cast<const int32_t*&>(buffer)++);
        if (len & 2)
            AParcel_writeChar(parcel, *reinterpret_cast<const char16_t*&>(buffer)++);
        if (len & 1)
            AParcel_writeByte(parcel, *reinterpret_cast<const int8_t*&>(buffer));
        return STATUS_OK;
    };
}();

static inline void sendMessage(int fd, int num, const AParcel* parcel) {
    int32_t size = AParcel_getDataSize(parcel);
    const int32_t header[] = { num, size };
    if (size > 0) {
        auto buffer = new int64_t[(size + sizeof(int64_t) - 1) / sizeof(int64_t) + 1];
        buffer[0] = *reinterpret_cast<const int64_t*>(header);
        marshalParcel(parcel, reinterpret_cast<uint8_t*>(&buffer[1]), 0, size);
        send(fd, buffer, sizeof(header) + size, 0);
        delete[] buffer;
    } else {
        send(fd, header, sizeof(header), 0);
    }
}

static inline int recvMessage(int fd, AParcel* parcel) {
    int32_t header[2] = { 0 };
    if (recv(fd, header, sizeof(header), MSG_WAITALL) == sizeof(header)) {
        const int32_t size = header[1];
        if (size > 0) {
            auto buffer = new int64_t[(size + sizeof(int64_t) - 1) / sizeof(int64_t)];
            if (recv(fd, buffer, size, MSG_WAITALL) == size || (header[0] = 0)) {
                unmarshalParcel(parcel, reinterpret_cast<const uint8_t*>(buffer), size);
                AParcel_setDataPosition(parcel, 0);
            }
            delete[] buffer;
        } else {
            AParcel_reset(parcel);
        }
    }
    return header[0];
}

static inline void sendFileDescriptor(int fd, int value) {
    struct msghdr msg = {nullptr};
    char control[CMSG_SPACE(sizeof(int))];
    uint8_t unused = 0;
    struct iovec iov = {.iov_base = &unused, .iov_len = sizeof(unused)};
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = control;
    msg.msg_controllen = sizeof(control);
    struct cmsghdr *ctrl_msg = CMSG_FIRSTHDR(&msg);
    ctrl_msg->cmsg_level = SOL_SOCKET;
    ctrl_msg->cmsg_type = SCM_RIGHTS;
    ctrl_msg->cmsg_len = CMSG_LEN(sizeof(int));
    *(int *) CMSG_DATA(ctrl_msg) = value;
    sendmsg(fd, &msg, 0);
}

static inline int recvFileDescriptor(int fd) {
    uint8_t unused;
    struct msghdr msg = {nullptr};
    struct cmsghdr *ctrl_msg = nullptr;
    char control[CMSG_SPACE(sizeof(int))];
    struct iovec iov = { .iov_base = &unused, .iov_len = sizeof(unused) };
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = control;
    msg.msg_controllen = sizeof(control);
    if (recvmsg(fd, &msg, MSG_WAITALL) >= 0 &&
        (ctrl_msg = CMSG_FIRSTHDR(&msg)) &&
        ctrl_msg->cmsg_level == SOL_SOCKET &&
        ctrl_msg->cmsg_type == SCM_RIGHTS)
        return *(int *) CMSG_DATA(ctrl_msg);
    return -1;
}